﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SharpGameLibrary
{
    public class Map
    {
        #region Fields

        static int heightInTile = 32;
        static int widthInTile = 32;

        string name;
        int index;
        Dictionary<int, string> tilesets;
        List<Layer> layers;
        Attribute[,] attributes;
        Entity[,] entities;
        MapData datas;
        Tuple<int, int, int, int, int, int, int, Tuple<int>> adjacentMaps;

        #endregion

        #region Get / Set

        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }

        public int Index
        {
            get
            {
                return this.index;
            }
            set
            {
                this.index = value;
            }
        }

        public static int HeightInTile
        {
            get
            {
                return Map.heightInTile;
            }
        }

        public static int WidthInTile
        {
            get
            {
                return Map.widthInTile;
            }
        }

        public string IndexName
        {
            get
            {
                return this.index + " : " + this.name;
            }
        }

        public List<Layer> Layers
        {
            get
            {
                return this.layers;
            }
        }

        public Attribute[,] Attributes
        {
            get
            {
                return this.attributes;
            }
        }

        public Entity[,] Entities
        {
            get
            {
                return this.entities;
            }
        }

        public Tuple<int, int, int, int, int, int, int, Tuple<int>> AdjacentMaps
        {
            get
            {
                return this.adjacentMaps;
            }
            set
            {
                this.adjacentMaps = value;
            }
        }

        #endregion

        #region Constructor

        public Map(int index)
            : this(index, "")
        {
        }

        public Map(int index, string name)
            : this(index, name, new Dictionary<int, string>(), new List<Layer>(), Map.initializeAttributes(), Map.initializeEntities(),
            new Tuple<int, int, int, int, int, int, int, Tuple<int>>(-1, -1, -1, -1, -1, -1, -1, new Tuple<int>(-1)))
        {
        }

        public Map(int index, string name, Dictionary<int, string> tilesets, List<Layer> layers,
            Attribute[,] attributes, Entity[,] entities, Tuple<int, int, int, int, int, int, int, Tuple<int>> adjacentMaps)
        {
            this.index = index;
            this.name = name;
            this.tilesets = tilesets;
            this.layers = layers;
            this.attributes = attributes;
            this.entities = entities;
            this.adjacentMaps = adjacentMaps;

            this.datas = new MapData();
        }

        #endregion

        #region Methods

        public void addTileset(string tileset)
        {
            tilesets.Add(this.tilesets.Count, tileset);
        }

        public void createLayer(string layerName)
        {
            layers.Add(new Layer(layerName, Map.widthInTile, Map.heightInTile));
        }

        public bool haveTileset(string tileset)
        {
            foreach (string elem in this.tilesets.Values)
                if (tileset == elem)
                    return true;
            return false;
        }

        #endregion

        #region private static Init

        private static Attribute[,] initializeAttributes()
        {
            Attribute[,] attributes = new Attribute[Map.widthInTile, Map.heightInTile];

            for (int y = 0; y < Map.heightInTile; y++)
                for (int x = 0; x < Map.widthInTile; x++)
                    attributes[x, y] = new Attribute();

            return attributes;
        }

        private static Entity[,] initializeEntities()
        {
            Entity[,] entities = new Entity[Map.widthInTile, Map.heightInTile];

            for (int y = 0; y < Map.heightInTile; y++)
                for (int x = 0; x < Map.widthInTile; x++)
                    entities[x, y] = new Entity();

            return entities;
        }

        #endregion

        #region Save / Load

        public void save(FileStream fs)
        {
            this.datas.store(this);

            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(fs, this.datas);
        }

        public void load(FileStream fs, bool isOnEditor)
        {
            BinaryFormatter formatter = new BinaryFormatter();

            this.datas = (MapData)formatter.Deserialize(fs);

            this.name = this.datas.Name;
            this.index = this.datas.Index;

            this.adjacentMaps = new Tuple<int, int, int, int, int, int, int, Tuple<int>>(this.datas.NorthMap, this.datas.EastMap, this.datas.SouthMap, this.datas.WestMap,
                this.datas.NorthEastMap, this.datas.SouthEastMap, this.datas.SouthWestMap, new Tuple<int>(this.datas.NorthWestMap));

            for (int i = 0; i < this.datas.Tilesets.Length; i++)
                this.addTileset(this.datas.Tilesets[i]);

            for (int i = 0; i < this.datas.Layers.GetLength(0); i++)
            {
                this.layers.Add(new Layer(i.ToString(), Map.widthInTile, Map.heightInTile)); //TODO put the name of the layer

                for (int pos = 0; pos < Map.widthInTile * Map.heightInTile; pos++)
                    if (this.datas.Layers[i, pos].Item2 != -1)
                    {
                        this.layers[i].setTile(this.datas.Layers[i, pos].Item1,
                            this.datas.Layers[i, pos].Item2 % WorldMap.getTileset(this.datas.Layers[i, pos].Item1).MaxX,
                                this.datas.Layers[i, pos].Item2 / WorldMap.getTileset(this.datas.Layers[i, pos].Item1).MaxX,
                                pos % Map.widthInTile,
                                pos / Map.widthInTile);
                    }
            }

            for (int pos = 0; pos < Map.widthInTile * Map.heightInTile; pos++)
            {
                this.attributes[pos % Map.widthInTile, pos / Map.widthInTile] = new Attribute(this.datas.IsBlocked[pos]);
                //this.attributes[pos % Map.widthInTile, pos / Map.widthInTile].IsDeath = this.datas.IsDeath[pos];
                //goto ligne precedente À DÉCOMMENTER
                this.entities[pos % Map.widthInTile, pos / Map.widthInTile] = new Entity(this.datas.Entities[pos]);
            }
        }

        #endregion

        //TODO compression
        #region Data

        [Serializable]
        private class MapData
        {
            #region Fields

            string name;
            int index;
            int heightInTile;
            int widthInTile;
            string[] tilesets;
            Tuple<string, int>[,] layers;
            bool[] isBlocked;
            bool[] isDeath;
            int[] entities;
            int northMap;
            int eastMap;
            int southMap;
            int westMap;
            int northEastMap;
            int southEastMap;
            int southWestMap;
            int northWestMap;

            #endregion

            #region Getters

            public string Name
            {
                get
                {
                    return this.name;
                }
            }

            public int Index
            {
                get
                {
                    return this.index;
                }
            }

            public int HeightInTile
            {
                get
                {
                    return this.heightInTile;
                }
            }

            public int WidthInTile
            {
                get
                {
                    return this.widthInTile;
                }
            }

            public string[] Tilesets
            {
                get
                {
                    return this.tilesets;
                }
            }

            public Tuple<string, int>[,] Layers
            {
                get
                {
                    return this.layers;
                }
            }

            public bool[] IsBlocked
            {
                get
                {
                    return this.isBlocked;
                }
            }

            public bool[] IsDeath
            {
                get
                {
                    return this.isDeath;
                }
            }

            public int[] Entities
            {
                get
                {
                    return this.entities;
                }

            }

            public int NorthMap
            {
                get
                {
                    return this.northMap;
                }
            }

            public int EastMap
            {
                get
                {
                    return this.eastMap;
                }
            }

            public int SouthMap
            {
                get
                {
                    return this.southMap;
                }
            }

            public int WestMap
            {
                get
                {
                    return this.westMap;
                }
            }

            public int NorthEastMap
            {
                get
                {
                    return this.northEastMap;
                }
            }

            public int SouthEastMap
            {
                get
                {
                    return this.southEastMap;
                }
            }

            public int SouthWestMap
            {
                get
                {
                    return this.southWestMap;
                }
            }

            public int NorthWestMap
            {
                get
                {
                    return this.northWestMap;
                }
            }

            #endregion

            #region Constructor

            public MapData()
            {
            }

            #endregion

            #region Save

            public void store(Map map)
            {
                this.name = map.name;
                this.index = map.index;

                this.northMap = map.adjacentMaps.Item1;
                this.eastMap = map.adjacentMaps.Item2;
                this.southMap = map.adjacentMaps.Item3;
                this.westMap = map.adjacentMaps.Item4;
                this.northEastMap = map.adjacentMaps.Item5;
                this.southEastMap = map.adjacentMaps.Item6;
                this.southWestMap = map.adjacentMaps.Item7;
                this.northWestMap = map.adjacentMaps.Rest.Item1;

                Dictionary<string, int> tilesetsIndex = new Dictionary<string, int>();

                this.tilesets = new string[map.tilesets.Count];
                for (int i = 0; i < map.tilesets.Count; i++)
                {
                    this.tilesets[i] = map.tilesets[i];
                    tilesetsIndex.Add(this.tilesets[i], i);
                }

                this.layers = new Tuple<string, int>[map.layers.Count, Map.widthInTile * Map.heightInTile];
                for (int i = 0; i < map.layers.Count; i++)
                    for (int y = 0; y < Map.heightInTile; y++)
                        for (int x = 0; x < Map.widthInTile; x++)
                            if (map.layers[i].getTilesetName(x, y) == null)
                                this.layers[i, x + y * Map.widthInTile] = new Tuple<string, int>("", -1);
                            else
                                this.layers[i, x + y * Map.widthInTile] = new Tuple<string, int>(
                                    map.layers[i].getTilesetName(x, y),
                                    map.layers[i].getTilePosXInTileset(x, y) + map.layers[i].getTilePosYInTileset(x, y) *
                                    WorldMap.getTileset(map.layers[i].getTilesetName(x, y)).MaxX);

                this.isBlocked = new bool[Map.widthInTile * Map.heightInTile];
                this.isDeath = new bool[Map.widthInTile * Map.heightInTile];
                this.entities = new int[Map.widthInTile * Map.heightInTile];
                for (int y = 0; y < Map.heightInTile; y++)
                    for (int x = 0; x < Map.widthInTile; x++)
                    {
                        this.isBlocked[x + y * Map.widthInTile] = map.attributes[x, y].IsBlocked;
                        this.isDeath[x + y * Map.widthInTile] = map.attributes[x, y].IsDeath;
                        this.entities[x + y * Map.widthInTile] = map.entities[x, y].Id;
                    }
            }

            #endregion
        }

        #endregion
    }
}
